装饰器, 装饰器原理, 装饰器的好处, 装饰器带来的副作用及解决方法, 带参装饰器及应用
装饰器
- 装饰器是可调用的对象, 其参数是另一个函数(被装饰的函数) 。 装饰器可能会处理被装饰的函数, 然后把它返回, 或者将其替换成另一个函数或可调用对象
- 它经常用于有切面需求的场景:如 插入日志、性能测试、事物处理、缓存、权限校验等等
装饰器的推导式1
- 需求
一个加法函数,想要增强它的功能,能够输出被调用过以及调用的参数信息
1 | def add(x,y): |
上面的加法函数是完成了需求,但是有以下缺点
- 打印语句的耦合度太高,如果有100个函数需要同样的功能,则这100个函数都需要添加相同的代码
- 加法函数属于业务功能,而输出信息的功能,属于非业务功能代码,不该放在业务函数加法当中
为了做到业务功能分离,我们可以利用高阶函数进行将功能函数与业务函数进行分类
1 | def add(x,y): |
遗留问题
以上功能函数也就是业务函数已经实现分离,但是问题在于参数的传入,此时函数的参数是在函数体内固定死了,但是现实中的函数参数是灵活的,不可能一成不变的,因此需要对参数进行选择性传入
装饰器的推导式2 自由参数传入
- 为了解决自由参数的传入
- 在嵌套函数中,外层函数的形参对于外层函数就是本质的形参,但是对于内层函数就是实参。因此外层函数传入实参时,经过外层 args, ** kwargs贪婪模式吸收,形成tuple和dict的形式,传递给内层函数,内层函数拿到就是tuple和dict,再经过实参出入时解构 args,** kwargs。最后就将外层函数传入的实参梯队传入内层函数。
1 | # 解决只有参数的传入,原本函数如下: |
1 | # 为了解决只有参数的传入 |
Call function begin
Call function end
11
装饰器的推导式3 柯里化
- 为了将fn(x,y)变形为fn(x)(y)语法糖格式的需求,将函数进行柯里化
- 为了将logger(add,4,5)变形为logger(add)(5,6)
- 用到了闭包的概念
- 依据柯里化变形三步骤
- 函数中第一个或某一个参数提升至外层函数,并对外层函数重命名
- 原本命名的函数参数抛出提升,其余的变形为新命名函数的函数体
- 新命名函数返回值为原本之前的函数
1 | # 新函数的柯里化变形 |
Call function begin
Call function end
9
装饰器的推导式4 语法糖
- 上述变形已经实现柯里化调用,但是每次需要通过增强功能函数调用
- 为了解决通过功能函数调用业务函数的问题,直接采用语法糖,抛弃功能函数调用业务函数,直接采用功能函数调用,这会产生对外功能函数没发生什么变化的错觉,但是功能却已经增强了
1 | def logger(fn): |
Call function begin
Call function end
9
为什么要进行柯里化
柯里化就是为了配合语法糖,语法糖相当于将语法糖下面的函数作为实参传递给语法糖函数
原函数去哪里了
1 |
|
既然add已经不再是原来的 add 函数,那么增强功能函数要怎么调用 x+y 呢,此时就要用到了闭包的作用了
- 首先: 将函数add作为实参传递给语法糖函数,这是语法糖函数就产生了闭包,内存函数fn就已经记录下add函数了
- 其次: 将语法糖返回的函数覆盖现有的add函数
- 最后: add函数就等价于 _logger函数了, 可以通过调用 _ name 或者 _ doc _ 属性进行验证
装饰器的分类
无参装饰器
根据如上推导公式即可得到无参装饰器
1 | def logger(fn): |
Call function begin
Call function end
9
带参装饰器
- 带参装饰器它是一个函数
- 函数作为它的参数
- 返回值是一个不带参的装饰器函数
- 可以看做是在装饰器外层又加上一层函数,三层嵌套函数
- 带参装饰器的参数可以有多个,使用闭包功能进行传参
- 使用 @functionname(参数列表)的方式调用,等价于add = logger(参数列表)(add)
- 格式如下:
1 | import datetime |
Call logger begin
Call logger end
so fast
9
返回值为装饰函数
- 根据带参装饰器和不带参装饰器可知,经过装饰后的函数已经不再是原本的函数了,如以上的add函数已经变成_logger函数,因此想要知道函数的具体内容只能通过查看函数的 add 属性和 doc 属性可知,原本的函数已经被装饰器属性覆盖了,是因为功能函数已经传递给装饰器函数了,但是属性尚未传递
- 解决思路; 可以通过传递功能函数的同时,传递属性,此时外界看来一切都没有变化,只是简单的装饰过而已,无法看到装饰器内部复杂的变化
- 解决方法1:在装饰器中,手动将装饰器得到的属性修改为要装饰的属性
1 | wrapper.__name__ = wrapped.__name__ |
- 在装饰器中,调用修改属性函数,外界定义修改属性函数
1 | def property(wapped,wapper): |
- 通过装饰器进行修改属性,但是装饰器返回的不是另一个函数,返回的就是自己,由修改属性函数柯里化,返回装饰器函数即可
1 | def propety(wapped): |
举例:
1 | import datetime |
_logger
Call logger begin
1.000784
Call logger end
9 add This is add function
装饰器的副作用
有上述的案例可知,要被装饰的函数在给装饰器通过闭包传递功能时,尚未把属性传递,使得我们看到的add函数属性是_logger的属性,而不是add的属性。
- 解决方式1:函数思路解决问题,定义一个拷贝函数属性的函数
使用函数就是调用此函数,并给函数传参,wapper表示装饰器函数,wapped表示被装饰函数,只要传入这两个参数即可
1 | import datetime |
Call _logger begin
#####################
Call _logger end
2.000202
return: 9
name: add
doc: This is add function
装饰器案例
1 | import datetime |
Call _logger begin
Call _logger end
1.000573
return:9
name:add
doc:This is add function